// (C) Copyright to Kevin. Applied to MIT LICENSE

#include "BlockEngineGameMode.h"

#include "ConstructorHelpers.h"
#include "Materials/MaterialInstanceDynamic.h"
#include "Engine/World.h"
#include "EmptyActor.h"

#include "urk_pb/Handshake.pb.h"


ABlockEngineGameMode* ABlockEngineGameMode::instance = NULL;

// Sets default values
ABlockEngineGameMode::ABlockEngineGameMode()
{
	ABlockEngineGameMode::instance = this;

	ConstructorHelpers::FObjectFinder<UMaterialInterface> mat(TEXT("Material'/Game/blocks.blocks'"));
	// material = UMaterialInstanceDynamic::Create(mat.Object, this);
	material = mat.Object;
}

// Called when the game starts or when spawned
void ABlockEngineGameMode::StartPlay()
{
	AGameModeBase::StartPlay();

	chunk_container = this->GetWorld()->SpawnActor<AEmptyActor>();

	threadpool = FQueuedThreadPool::Allocate();
	bool succ = threadpool->Create(4);
	UE_LOG(BlockEngine, Log, TEXT("ThreadPool creation = %s"), succ ? TEXT("OKAY") : TEXT("FAILED"));

	// managers 
	texture_manager = new TextureManager();
	texture_manager->Initialize();
	block_manager = new BlockManager();
	block_manager->Initialize();

	network = new NetworkClient();
	network->cb_on_connected = [&]() {
		urk::pb::ClientHandshakeMessage msg;
		msg.set_clientversion(0);
		msg.set_renderdistance(16);
		network->SendMessage(msg);
	};
	network->Start();

	
	//TArray<AActor *> * actors = new TArray<AActor *>();
	//GetAttachedActors(*actors);
	//for (AActor* child : *actors) {
	//	UE_LOG(BlockEngine, Log, TEXT("Label => %s"), *child->GetActorLabel());
	//}
	//delete actors;
	

	UE_LOG(BlockEngine, Log, TEXT("Chunk container is okay? = %s"), chunk_container ? TEXT("OKAY") : TEXT("NULL"));

	//{
	//	FChunkCoordinate coord{ -1, 5, 5 };
	//	#define BEDBG(fmt_str, ...) UE_LOG(BlockEngineDebug, Log, TEXT(fmt_str), __VA_ARGS__);
	//	BEDBG("COORD { x=%d, z=%d, y=%d };", coord.x, coord.z, coord.y);
	//	FChunkCoordinate coord_x_p = coord.GetXPlus();
	//	BEDBG("COORD X-PLUS { x=%d, z=%d, y=%d };", coord_x_p.x, coord_x_p.z, coord_x_p.y);
	//	FChunkCoordinate coord_x_m = coord.GetXMinus();
	//	BEDBG("COORD X-MINUS { x=%d, z=%d, y=%d };", coord_x_m.x, coord_x_m.z, coord_x_m.y);
	//	FChunkCoordinate coord_z_m = coord.GetZMinus();
	//	BEDBG("COORD Z-MINUS { x=%d, z=%d, y=%d };", coord_z_m.x, coord_z_m.z, coord_z_m.y);
	//	FChunkCoordinate coord_comp{ -1, 5, 5 };
	//	BEDBG("COORD COMPARE? = %s", coord_comp.Equals(coord) ? TEXT("Y") : TEXT("N"));
	//}

	/* for (int cx = -2; cx < 2; cx++) {
		for (int cz = -2; cz < 2; cz++) {
			SpawnTestChunks(cx, cz);
		}
	} */
}

void ABlockEngineGameMode::BeginDestroy()
{
	Super::BeginDestroy();

	if (network) {
		network->Shutdown();
		network = NULL;
	}
}

NetworkClient * ABlockEngineGameMode::GetClient()
{
	return network;
}

AActor * ABlockEngineGameMode::GetChunkContainer()
{
	return chunk_container;
}

UMaterialInterface * ABlockEngineGameMode::GetMaterial()
{
	return material;
}

FQueuedThreadPool * ABlockEngineGameMode::GetThreadPool()
{
	return threadpool;
}

BlockManager * ABlockEngineGameMode::GetBlockManager()
{
	return block_manager;
}

TextureManager * ABlockEngineGameMode::GetTextureManager()
{
	return texture_manager;
}

AChunkActor * ABlockEngineGameMode::FindChunk(const FChunkCoordinate& coordinate)
{
	for (AChunkActor* chunk : chunks) {
		if (chunk->GetChunkCoordinate() == coordinate) return chunk;
	}
	return NULL;

	//TArray<AActor*> children = TArray<AActor*>();
	//chunk_container->GetAttachedActors(children);

	//for (AActor* child : children) {
	//	AChunkActor* chunk = dynamic_cast<AChunkActor*>(child);
	//	if (!chunk) continue; // WTF?
	//	if (chunk->GetChunkCoordinate().Equals(coordinate)) return chunk;
	//}
	//return NULL;
}

ABlockEngineGameMode* ABlockEngineGameMode::GetInstance() {
	return instance;
}

void ABlockEngineGameMode::SpawnTestChunks(int x, int z)
{
	for (int y = 0; y < 16; y++) {
		// test
		AChunkActor* test_chunk = GetWorld()->SpawnActor<AChunkActor>(AChunkActor::StaticClass(), FTransform());
		ChunkData* data = new ChunkData();
		for (int cx = 0; cx < 16; cx++) {
			for (int cy = 0; cy < 16; cy++) {
				for (int cz = 0; cz < 16; cz++) {
					int total_y = (y << 4) | cy;
					if (total_y < 62) {
						data->id[BlockIndex(cx, cy, cz)] = 1;
					}
					else if (total_y < 64) {
						data->id[BlockIndex(cx, cy, cz)] = 2;
					}
					else if (total_y == 64) {
						data->id[BlockIndex(cx, cy, cz)] = 3;
					}
				}
			}
		}
		test_chunk->Initialize(FChunkCoordinate{ x, z, (uint8)y }, data);
	}
}

void ABlockEngineGameMode::RegisterChunk(AChunkActor * chunk)
{
	if (!chunk) return;
	int32 index = chunks.Add(chunk);
	chunk->chunk_index = index;
}

void ABlockEngineGameMode::UnregisterChunk(AChunkActor * chunk)
{
	if (!chunk) return;
	if (chunk->chunk_index == -1) return;
	chunks.RemoveAt(chunk->chunk_index);
}

